  Inhalt nach Zeilennummern:

  Hinweise  ................................................   28
  Compiler, Standardprozeduren und Laufzeitsystem  .........   57
     Schlsselwort CODE  ...................................   66
     Standardprozedur SHIFT  ...............................  142
     Standardprozedur SIZE  ................................  192
     Standardprozedur VAL  .................................  226
     Standardprozedur ADR  .................................  259
     Parametertypen der Standardprozeduren  ................  281
     Automatische Typkonvertierung  ........................  325
     (LONG)REAL-Arithmetik  ................................  352
     LONG-Konstanten  ......................................  423
     Deklaration von Stringvariablen/typen  ................  517
     Lokale Module  ........................................  537
  Genderte TRAP-Vektoren  .................................  576
     Lader  ................................................  579
     LPR-Modul "Process"  ..................................  632
  Statischer Speicherbedarf ( Linker, "GEMX", "Heap" )  ....  643
  Assemblerbenutzung  ......................................  754
     Testmodus  ............................................  757
     Variablenplazierung  ..................................  786
     Verwendung der Systemregister  ........................  841
     Prozeduraufrufe  ......................................  866
     Zugriff auf Parameter und lokale Variablen  ...........  913
     Umgebung der Systemregister A4 und A6 zur Laufzeit  ... 1022
  Aufbau einer OBM-Datei  .................................. 1197


  Hinweise
  ========================================================================
  Die hier wiedergegebenen Informationen habe ich durch eigene Versuche
  und Disassembler-Lufe (und Nachdenken...) herausgefunden, auerdem
  ist nicht gesagt, da sie auch fr andere Versionen von LPR-Modula als
  1.4 gelten, deswegen:

      V E R W E N D U N G    A U F    E I G E N E    G E F A H R


  Trotz der mehr oder weniger zahlreichen Beispiele drften einige Abschnitte
  schwer verstndlich sein - wer also etwas nicht versteht, Fehler entdeckt
  hat oder weitergehende Informationen besitzt, kann mir schreiben; die
  Adresse steht am Ende des README.

  Der Text liest sich vielleicht etwas wie eine Zettelsammlung, und das
  liegt daran, da tatschlich immer wieder Neues dazugekommen ist, wenn
  ichs denn herausgefunden hatte. Das ganze ist also kein Handbuch des
  LPR-Systems (dafr fehlen sowieso noch eine Menge Informationen, die ich
  selber gern htte. Interessant wre vor allem eine Dokumentation der
  Prozeduren aus 'M2LOADER.*'; wer ber dieses Modul etwas herausgefunden
  hat: bitte schreiben ).

  Wenn in den Beispielen Variablen benannt werden, so geschieht dies mit
  Hinblick auf den Typ der Variablen; so ist also eine Variable mit Namen
  'int' eine INTEGER-Variable.



  Compiler, Standardprozeduren und Laufzeitsystem
  ====================================================================

  In der Importliste unterscheidet der Compiler bei den Modulnamen nicht
  zwischen Gro- und Kleinbuchstaben, auer bei 'SYSTEM' bzw. 'System'.
  Auerdem werden bei den Modulnamen nur 16 Zeichen unterschieden, wogegen
  es bei Prozedur- und Variablennamen mindestens 255 sind.


  Schlsselwort CODE
  ----------------------------------------------------------------------
  SYSTEM exportiert eine Pseudoprozedur zur Einbettung von Assemblercode in
  den Quelltext: INLINE. Im Gegensatz zu INLINE eignet sich die Pseudoprozedur
  CODE, die Teil der Schlsselworte ist und nicht importiert werden darf,
  dazu, im MODULA-Programmtext Betriebssystemaufrufe oder andere Funktionen,
  fr die man TRAPs braucht, abzusetzen (siehe DEF.Modul Process und
  TRAPDefs).
  Hierzu plaziert man ein CODE-Statement direkt unter eine Prozedurdeklaration
  anstelle des Prozedurrumpfes (gleiche Syntax wie das FORWARD-Statement).
  Wird diese Prozedur aufgerufen, so werden zuerst die entsprechenden
  Prozedurparameter auf dem Stack abgelegt, und dann das 16-Bit-Befehlswort
  ausgefhrt, das vom Compiler anstelle des Prozeduraufrufs in den Code
  eingebettet wurde; fr dieses Befehlswort eignen sich natrlich die TRAP-
  Befehle sehr gut, mit denen die Betriebssystemroutinen aufgerufen werden.
  Zu beachten ist, da die Parameter vom Compiler in der Reihenfolge ihres
  Auftretens in der Parameterliste auf den Stack gebracht werden, d.h.
  umgekehrt wie bei 'C'-Compilern (die Funktionsnummer ist also der LETZTE
  Parameter in der Liste). Da die meisten Beispiele in Bchern fr
  Betriebssystemaufrufe in 'C' gehalten sind, mssen die angegebenen Parameter
  in umgekehrter Reihenfolge geschrieben werden.
  Da die Betriebssystemroutinen meistens ihren Funktionswert in Register D0
  zurckgeben, mu dieser anschlieend noch mit  REG(0) und evtl.
  Typkonvertierung einer Variablen zugewiesen oder als Funktionswert
  zurckgegeben werden.


    Beispiel:
    ---------

      CONST Akku   = 0; (* M68000-Register D0 *)
            Getrez = 4; (* Xbios-Funktionsnr. *)

      PROCEDURE getrez():CARDINAL;

        PROCEDURE XBIOS (Nr : CARDINAL); CODE(4E4EH); (* XBIOS-Trap *)

        BEGIN (* getrez *)
          XBIOS( Getrez );
          RETURN( SHORT( REG( Akku )));

          (* wird zu:
           *   move.w  #Getrez, -(SP)
           *   trap    #XBIOS
           *   move.w  D0, RETURN(A6)
           *)
        END getrez;

  Vorsicht ist geboten, falls der Aufruf einer solchen Prozedur in einer
  Schleife geschieht: da der Stack nicht automatisch korrigiert wird
  (Parameter entfernen), wchst er immer weiter an...
  Bei einem einmaligen Aufruf macht das nichts, da bei Prozedurende der bei
  Prozedureintritt gesicherte (LINK ...) Stackpointer wieder
  zurckgeschrieben wird (UNLK ...), und somit smtliche eventuell auf dem
  Stack noch liegenden Werte wieder entfernt werden.
  (Bei einem wiederholten Aufruf innerhalb der Prozedur wird der Stack am
  Ende der Prozedur natrlich auch wieder bereinigt, aber wenn z.B. in einer
  Schleife 1000-mal Parameter von vielleicht 20 Bytes auf dem Stack abgelegt
  werden, wchst womglich der Stack in andere Speicherbereiche hinein -
  einen Stacktest gibt es bei diesem Compiler ja leider nicht).

  ! Auf jeden Fall mu aber der Stack korrigiert werden, wenn im Kopf
  ! des entsprechenden Moduls eine Priorittsangabe steht, da hierbei
  ! nach dem Prozedureintritt der bisherige Inhalt des Statusregisters
  ! gerettet wird, und vor Prozedurende, also vor dem UNLK, wieder
  ! zurckgeschrieben wird; hierzu mu der Stackpointer natrlich auf
  ! dem Wert stehen, den er direkt nach dem Retten hatte.


  Hinweis: Die explizite Benutzung von CODE kann durch die Benutzung des
  Moduls 'TRAPDefs' vermieden werden, in dem auch Definitionen zur
  Stackkorrektur stehen. Einfacher ist es, die Betriebssystemmodule GEMDOS*,
  BIOS* und XBIOS* zu benutzen, es sei denn, Platzsparen wre wichtig.



  Standardprozedur SHIFT
  ---------------------------------------------------------------------
  Das Pseudomodul 'SYSTEM' exportiert eine Funktion 'SHIFT':

      stdtyp1 := SHIFT( stdtyp2, shift );

  Fr 'stdtyp' kann ein beliebiger Standarddatentyp oder ein Unterbereichstyp
  auer REAL oder LONGREAL stehen. Die Funktion weist der Variablen 'stdtyp1'
  den um <shift> Bits nach rechts oder links verschobenen Wert von 'stdtyp2'
  zu.

     shift < 0 : um <shift> Bits nach rechts schieben
     shift > 0 :          -"-         links     "

  <shift> wird MODULO 32 behandelt, d.h <shift> = 33 q. <shift> = 1.
  Ist 'stdtyp2' vom Typ INTEGER, LONGINT oder ein Unterbereich vom Typ
  INTEGER, wird arithmetisch verschoben, sonst logisch.

  Beispiele fr die Umsetzung des SHIFT-Befehls in Assemblercode:

           :

     VAR  bs : BITSET;
          li : LONGINT;

           :

     bs := SHIFT( bs, 13 );

        (* wird zu:
         *    move.w bs(A6), D2
         *    lsl.w  #5, D2
         *    lsl.w  #8, D2
         *    move.w D2, bs(A6)
         *)

     li := SHIFT( li, -5 );

        (* wird zu
         *    move.l li(A6), D2
         *    asr.l  #-5, D2
         *    move.l D2, li(A6)
         *)

  Das funktioniert auch, wenn <shift> eine Variable ist; diese wird immer als
  Zahl mit Vorzeichen interpretiert, so da zur Laufzeit entschieden wird, in
  welche Richtung zu schieben ist.



  Standardprozedur SIZE
  ------------------------------------------------------------------
  SIZE gibt bei allen Deklarationen die Anzahl Bytes Speicherbedarf an,
  auch bei einzelnen Bytegren innerhalb eines RECORD wird die Gre
  1 ermittelt, obwohl nachfolgende Variablen, auer weiteren Bytegren,
  auf eine gerade Adresse gelegt werden, womit der Speicherbedarf effektiv
  doch wieder 2 Bytes betrgt, und fr den Gesamtspeicherbedarf eines
  RECORD's immer eine gerade Anzahl Bytes ermittelt wird. Das fhrt dazu,
  da die Summe der fr die einzelnen Variablen ermittelten Speichergren
  auch kleiner sein kann als der Wert des gesamten RECORD's.
  Bei ARRAY's wird der Speicherbedarf immer auf eine gerade Zahl aufgerundet.

  Durch diese, meiner Meinung nach, merkwrdige Verhaltensweise ist es wohl
  angebracht, bei Benutzung der Funktion etwas Vorsicht walten zu lassen.

  Bei Anwendung von SIZE auf einen Prozedurparameter vom Typ ARRAY OF ...
  passiert recht merkwrdiges. Normalerweise wird SIZE ja nur dazu benutzt,
  die Speichergre von Typen oder Variablen zu bestimmen, die bereits zur
  bersetzungszeit bekannt sind (vom Compiler wird dann nur eine Konstante
  eingesetzt); wird SIZE aber z.B. innerhalb einer Prozedur auf einen
  Parameter vom Typ ARRAY OF CHAR angewendet:

    card := SIZE(string); ,

  gibt es whrend des bersetzens zunchst einen Busfehler! Nun ist ein
  Busfehler in einem Compiler eigentlich schon recht merkwrdig, wird aber
  die Meldung ignoriert und einfach CR gedrckt, so kann man sich nachher
  mittels Disassembler davon berzeugen, da korrekter Code erzeugt wurde,
  nmlich der fr:

    card := HIGH(string) + 1;



  Standardprozedur VAL
  ------------------------------------------------------------------
  Der bliche Typtransfer, der es gestattet, die Typprfung des Compilers
  auer Kraft zu setzen, indem vorhandene Bitmuster anders interpretiert
  werden, fehlt in LPR-Modula; d.h. folgendes geht nicht:

    int1 := INTEGER(card) + int2;

  Stattdessen gibt es die aus SYSTEM zu importierende Pseudoprozedur VAL.
  Das vorige Beispiel wrde also so aussehen:

    int1 := VAL( INTEGER, card ) + int2;

  Normalerweise ist diese Prozedur nur fr die skalaren Datentypen gedacht,
  also INTEGER, CARDINAL, die LONG-Versionen, Unterbereichstypen und
  Aufzhlungstypen. Wohl wegen des fehlenden Typtransfers ist VAL in diesem
  MODULA aber universeller, es geht also z.B.:

    set := VAL(BITSET, card) * {0..3};,

  d.h, es werden alle auer den unteren vier Bits gelscht; aber auch sowas
  ist mglich:

    longcard := VAL(LONGCARD, real);,

  da sowohl LONGCARD als auch REAL vier Bytes Speicherplatz bentigen.
  Was 'umzutypen' geht und was nicht, probiert man am besten selber aus,
  manchmal hilft auch der Umweg ber ein Zwischenergebnis mit zweimaligem
  Typtransfer (oder ein `varianter' RECORD).



  Standardprozedur ADR
  ------------------------------------------------------------------
  Die Funktion SYSTEM.ADR lt sich auf Stringkonstanten anwenden, d.h.
  es funktioniert sowohl:

    adrvar := ADR("String");

  als auch:

    CONST s = "String";

    adrvar := ADR(s);

  Auerdem lt sich mit ADR auch die Anfangsadresse einer Prozedur
  ermitteln:

    PROCEDURE p(...);

    adrvar := ADR(p);



  Parametertypen der Standardprozeduren
  ---------------------------------------------------------------------
  Folgende Standardprozeduren arbeiten mit INTEGER- statt der erwarteten
  CARDINAL-Werte

     ABS
     HIGH
     ORD
     LONG     * bei Anwendung auf CARDINAL
     SHORT    *        -"-        LONGCARD
     TRUNC
     TRUNCD
     FLOAT
     FLOATD

  Wobei mit 'arbeiten' der zur bersetzungszeit bekannte Funktionstyp gemeint
  ist (Meldung: 'incompatible Operand Types'); was dann zur Laufzeit
  produziert wird, ist manchmal etwas ganz anderes:

  Beispiel:
  ---------

    card := SHORT(longcard);

    Ist der Testmodus eingeschaltet, wird berprft, ob der Wert
    MAX(INTEGER) berschritten wird - d.h. SHORT soll nur INTEGER-
    Werte produzieren.

    longcard := LONG( card );

    Hier wird <card> vor der Zuweisung auf LONGCARD erweitert ( das
    hherwertige Wort wird gelscht ), und auch bei eingeschaltetem
    Testmodus findet keine weitere berprfung statt, d.h. LONG
    liefert LONGCARD-Werte, obwohl der Compiler LONG als LONGINT-
    Funktion betrachtet (longcard := longcard + LONG(card);
    produziert den Typfehler).


  Bei der bernahme von Programmen aus der Literatur oder von Quellen, die
  fr andere Compiler geschrieben wurden, wird es also hufig ntig sein, die
  Ergebnisse der Standardfunktionen mit VAL zu CARDINAL bzw. LONGCARD zu
  `konvertieren'.


  Automatische Typkonvertierung
  -----------------------------------------------------------------------
  Bei der automatischen Anpassung der Lngen von INTEGER und CARDINAL- auf
  LONG-Zahlen ist Vorsicht angebracht: Soll z.B. der Absolutwert einer
  INTEGER-Zahl einer LONGCARD-Zahl zugewiesen werden, knnte man schreiben:

      lc := ABS(int);

  Ist aber <int> = MIN(INTEGER), so wird der Wert durch ABS() nicht gendert,
  da es fr diesen Wert keine entsprechende positive Zahl im Zweierkomplement
  gibt; was passiert? MIN(INTEGER) wird fr die Zuweisung vorzeichenrichtig
  auf LONGINT erweitert, als Kardinalzahl interpretiert ist das aber
  MAX(LONGCARD) - MAX(INTEGER), und nicht etwa MAX(INTEGER) + 1, wie erwartet.

  Abhilfe:

      lc := ABS( LONG( int ));

  Da hier zuerst auf LONGINT erweitert wird und dann der Absolutwert genommen
  wird, kann der Wertebereich nicht berschritten werden.


  Bei der Konvertierung von CARDINAL- und INTEGER-Zahlen und bei der Benutzung
  von Standardprozeduren ist also Wachsamkeit angesagt - hier hilft oft nur
  Probieren.


  (LONG)REAL-Arithmetik
  ---------------------------------------------------------------------
  Die Division von LONGREAL-Zahlen wird, falls nicht gerade durch eine
  Zweierpotenz dividiert werden soll, durch einen Fehler mit schtzungsweise
  32 statt 53 BIT Genauigkeit ausgefhrt.
  Verantwortlich ist die fehlerhafte LONGREAL-Divisionsroutine "FDIVd" aus
  dem Laufzeitsystem 'System'. Unter Kontrolle des Laders wird immer das in
  der Shell 'M2SHELL.OBM' vorhandene (siehe: 'Aufbau einer OBM-Datei')
  'System' benutzt. Die Datei SYSTEM.OBM ist hingegen nur fr die
  Laufzeituntersttzung `gelinkter' Programme zustndig.
  Da unter anderem auch der Compiler mit diesem Laufzeitmodul arbeitet,
  lassen sich auch LONGREAL-Konstanten im Programmtext nur mit dieser
  Genauigkeit - ca. 10 Dezimalstellen - angeben, da bei der Umwandlung der
  Zeichenketten auch die Division bentigt wird.


  Ebenfalls am Laufzeitmodul liegt es, da FLOAT vom Compiler her zwar
  negative Werte akzeptiert (FLOAT und TRUNC werden in Aufrufe von
  System.FLOATs bzw. System.TRUNCs umgesetzt, TRUNCD und FLOATD in
  System.TRUNCd und System.FLOATd), aber daraus Unsinn produziert;
  FLOATD hingegen verarbeitet auch negative Zahlen korrekt.

  Auerdem scheitert TRUNCD bei der Konvertierung zu MIN(LONGINT).


  Weiterhin funktioniert auch der Vergleich zweier LONGREAL-Zahlen nicht
  immer, es kann sein, da eine Zahl, die eigentlich grer als eine
  zweite ist, pltzlich als kleiner erkannt wird (und umgekehrt)!

  Beispiel:

    CONST Pi = 3.14159265358979323846264338327950288D;

    BEGIN
      lreal := PI;
      lreal := lreal + 1.0D-6;

      IF  lreal > Pi  THEN
        WriteString('Alles klar');
      ELSE
        WriteString('Mist, verdammter');
      END;

  Das Ergebnis dieses Vergleichs drfte berraschen...

  Schuld ist die Routine FCMPd aus dem Laufzeitmodul, die die zweiten Hlften
  der zu vergleichenden Zahlen als vorzeichenbehaftet betrachtet, obwohl
  das Vorzeichen nur bei den oberen Hlften eine Rolle spielt.


  Aber auch bei der Behandlung von REAL-Konstanten kommt der Compiler
  ins Stolpern:

    real := 3.9999998   gibt  den angegeben Wert, aber
    real := 3.9999999   gibt  6.0 !!!

  Schuld daran ist, so komisch das klingt, die (ebenfalls Laufzeit-)
  Routine FSHORT, die beim Runden Fehler macht. Anscheinend sind fr den
  Compiler smtliche Fliekommakonstanten vom Typ LONGREAL, so da REAL-
  Konstanten noch mit FSHORT umgewandelt werden, wenn kein 'D' in der
  Konstante auftaucht.



  ! Fr alle oben genannten Probleme gibt es eine Lsung in Form des    !
  ! Programms PATCH.TOS, das, wie schon der Name vermuten lt, das     !
  ! Laufzeitmodul, sowohl in der Shell als auch SYSTEM.OBM patcht. Die  !
  ! - kurzen - Erklrungen dazu stehen in PATCH.TXT bzw. PATCH.MOD.     !



  LONG-Konstanten
  --------------------------------------------------------------------
  LONGCARD- und LONGINT-Konstanten werden durch ein angehngtes 'D'
  gekennzeichnet. Sedezimale Konstanten mit angehngtem 'H' passen sich
  automatisch der bentigten Lnge an und drfen kein angehngtes 'D' haben.

    Beispiele fr LONGCARD/INT-Konstanten:

    1.23D   -487365142D   45FCH;

  Die Grundrechenarten Multiplikation und Division sind NICHT zwischen
  zwei LONGCARD/INT-Konstanten mglich.
    Folgendes geht also nicht:

    34516D * 12D   bzw.   34516D DIV 12D



  LONGREAL-Konstanten haben entweder ein angehngtes 'D', wenn die Zahl
  ohne Exponent geschrieben wird, oder das Exponenten-'E' mu durch ein
  'D' ersetzt werden (dann darf kein 'D' angefgt werden).

    Beispiele fr LONGREAL-Konstanten:

    1.23456789123D     4.896524314D-23


  Weder die vier Grundrechenarten noch ein Vergleich sind zwischen zwei
  LONGREAL-Konstanten mglich.
    Folgendes geht also nicht:

    1.2D * 3.4D4 , 1.2D / 3.4D4 , 1.2D + 3.4D4 , 1.2D - 3.4D4 , 1.2D < 3.4D


  Es knnen keine negativen LONGREAL-Konstanten deklariert werden.

    CONST neg = -1.2345D;    (* geht nicht *)


  Folgt direkt auf eine LONGREAL-Konstante, die mit einem 'D' abgeschlossen
  ist, ein '-' oder '+', so wird dieses als Vorzeichen eines nachstehenden
  Exponenten angesehen. Sollte Addition bzw. Subtraktion gemeint sein,
  mu zwischen dem 'D' und dem Additions/Subtraktionszeichen mindestens
  ein Leerzeichen stehen oder auch eine Null fr den Exponenten, ansonsten
  kann es zu lauter merkwrdigen Folgefehlern beim bersetzen kommen.

    Beispiel:

    1.234567D+lreal   (* Fehler ! *)

    1.234567D + lreal (* entweder so... *)

    1.234567D0+lreal  (* ...oder so *)



  Sollen zwei LONGREAL-Konstante miteinander verknpft werden, so knnen
  die Routinen aus dem Laufzeitsystem direkt als Prozeduren benutzt werden:


    FROM  System  IMPORT  FADDd, FSUBd, FMULd, FDIVd;


    FADDd( lrc1, lrc2 )      statt    lrc1 + lrc2
    FSUBd( lrc1, lrc2 )      statt    lrc1 - lrc2
    FMULd( lrc1, lrc2 )      statt    lrc1 * lrc2
    FDIVd( lrc1, lrc2 )      statt    lrc1 / lrc2


  Fr den Vergleich ist dies nicht mglich, da die Prozedur FCMPd ihr
  Resultat im Statusregister der CPU abliefert.
  Ebenso lassen sich die Routinen fr die LONGCARD/INT-Multiplikation
  und -Division nicht (so einfach) verwenden, da sowohl die Argumente
  als auch das Ergebnis in den CPU-Registern stehen.

  Da LONG-Konstanten (teilweise) nicht miteinander verknpft werden
  knnen, bedeutet aber auch, da der Compiler solche Ausdrcke nicht
  zur bersetzungszeit berechnet, sondern eine entsprechende Operation
  zur Laufzeit ausgefhrt wird. Deswegen ist es gnstiger, solche Berechnungen
  entweder von Hand auszurechnen und als Konstante hinzuschreiben oder
  mit Hilfe einer Variablen zu berechnen und zu speichern.

    Beispiel:

    lrconst := FADDd( lrc1, lrc2 );   oder

    lrconst := lrc1;
    lrconst := lrconst + lrc2;

  Die zweite Variante ist der ersten vorzuziehen, da hier nicht der
  ungewhnliche Import aus dem Laufzeitsystem ntig ist.



  Deklaration von Stringvariablen/typen
  --------------------------------------------------------------------
  Werden Stringvariablen mit einer ungeraden Anzahl Zeichen deklariert,
  z.B.:

    string := ARRAY [0..8] OF CHAR; ,

  so kann man diesen Strings um ein Zeichen lngere Stringkonstanten
  zuweisen, ohne da der Compiler meckert (siehe "Standardprozedur SIZE",
  "Variablenplazierung"); die Funktion HIGH( ) liefert dagegen den richtigen
  Maximalindex. Verwirrung kann es schaffen, wenn im Hauptprogramm der
  Stringvariablen ein (um ein Zeichen zu langer) Text zugewiesen wurde,
  und diese Variable einer Prozedur als Parameter dient; dann bearbeitet die
  Prozedur den String nmlich bis zu dem mit HIGH( ) festgestellten -
  korrekten - Maximalindex, und das letzte Zeichen fehlt dann pltzlich, z.B.
  bei einer Ausgabe.



  Lokale Module
  -------------
  Normalerweise mssen Prozeduren, die aus einem Definitionsmodul exportiert
  und in einem lokalen Modul implementiert werden, aus diesem lokalen Modul
  exportiert werden, damit sie sichtbar werden. Bei LPR mu man sie jedoch
  importieren!

  Andere Prozeduren, die zwar in einem lokalen Modul implementiert, aber
  nicht im Definitionsmodul exportiert werden, werden ganz normal aus
  dem lokalen Modul exportiert.


  Beispiel:

    DEFINITION MODULE export;
      PROCEDURE ex;
    END export.

    IMPLEMENTATION MODULE export;

      MODULE imp;

      IMPORT ex;  (* Hier muesste EXPORT stehen *)
      EXPORT lex; (* Wird ganz normal exportiert *)


      PROCEDURE ex;
      END ex;

      PROCEDURE lex;
      END lex;

      END imp;
    END export;

  Interessanterweise beherrscht LPR sogar Module innerhalb von Prozeduren.



  Genderte TRAP-Vektoren
  ======================================================================

  Lader
  ---------------------------------------------------------------------
  Whrend ein Programm unter Kontrolle des Laders luft, leitet dessen
  Laufzeitsystem einige Exceptions auf eigene Prozeduren um, nach Beendigung
  des Laders werden die alten Adressen wiederhergestellt.
  Was bei Exceptions wie Adrefehler, Busfehler usw. ganz praktisch ist,
  erweist sich bei zumindest einer anderen Umlenkung als fatal: Der Trap
  Nr. 11 wird - vom Compiler in den Code eingebettet - dazu verwendet, in
  den Supervisormodus zu schalten. Das wird immer dann bentigt, wenn eine
  Modulprioritt angegeben wird, z.B.:

    IMPLEMENTATION MODULE Monitor[7];

  in diesem Fall wird nmlich bei jeglicher dynamischer Benutzung des
  Moduls, also Modulinitialisierung und Prozeduraufrufe, die Interruptebene
  der CPU auf den angegebenen Priorittswert gesetzt - und dazu wird der
  Supervisormodus gebraucht.
  Eine Prioritt ist z.B. beim mitgelieferten 'Process'.Modul angegeben.
  Wird aber nun durch den Linker ein selbstndig lauffhiges Programm erzeugt,
  wird der Vektor fr Trap #11 nicht auf die/eine Routine zur Umschaltung des
  Modus umgesetzt (Das macht sonst das Laufzeitsystem des Laders).
  Da der Trap aber im Code steht, strzt das Programm wegen nicht
  initialisiertem Vektor ab, sobald der Initialisierungsteil eines solchen
  Moduls bei Programmstart ausgefhrt wird.
  Abhilfe wre ein kurzes Modul, das als erstes in jedes Programm importiert
  wird, welches ein anderes Modul mit Priorittsangabe direkt oder indirekt
  benutzt; in dessen Initialisierungsteil mte ein kurzes Codestck stehen,
  das den Trap #11-Vektor auf eine dort stehende Prozedur umsetzt, die den
  Modus wechselt. Auerdem mte dieses Modul eine Prozedur exportieren,
  die - bei Programmende aufgerufen - den alten Wert des Vektors wieder
  restauriert.
  Solange der Lader die Kontrolle ber das System hat, fhrt der Vektor
  auf folgende kleine Routine (disassembliert):


    TOSUPER:
      move.w  #$2700, SR        ; Keinen IR whrend der Aktion zulassen
                                ; ob das ntig ist ?
      ori.w   #$2700, (SP)      ; nach dem Trap auch durch nichts mehr
                                ; unterbrechbar und im Supervisormodus
      rte                       ; Rckkehr zum Aufrufer


  Fr den Code bei Modulen mit Prioritt wird im brigen der Befehl

      move  SR, D*

  im Usermodus benutzt; das geht nur beim 68000, sitzt im ATARI ein
  68010/20/30, gibt das eine Privilegverletzung; Mit diesen Prozessoren
  kann also beim gegebenen Compiler die Priorittsangabe nicht verwendet
  werden.


  LPR-Modul "Process"
  ---------------------------------------------------------------------
  Vom Modul 'Process' werden zustzlich noch die Traps 3 und 4 auf eigene
  Routinen gelenkt; das geschieht jedoch ohne Rcksicht auf das, was vorher
  in den Vektoren drinstand; mithin lassen sich die ursprnglichen Werte
  auch nicht mehr restaurieren. Das ist zwar in der Praxis nicht weiter
  schlimm, da die von TOS nicht benutzten Traps wohl auch von anderen
  Programmen nicht verwendet werden, aber auszuschlieen ist das auch nicht.



  Statischer Speicherbedarf ( Linker, "GEMX", "Heap" )
  ======================================================================

       Speicheraufteilung eines gelinkten Programms zur Laufzeit:
       ----------------------------------------------------------

                    ___________________
                   |                   |
                   |                   |  GEMDOS.Malloc/Mfree/Mshrink
  von LPR-MODULA   : TOS - HEAP        :
  unbenutzt        :                   :
                   |                   |
  =================|===================|===================================
                   |                   |  Prozeduraufrufe, lokale Variablen
    <stackSize>    : LPR - Stack       :
                   :                   :
                   |-------------------|
                   |                   |  `dynamische' Speicherverwaltung
    <heapSize>     : LPR - HEAP        :  mittels "Heap.Allocate/Deallocate"
                   :                   :
                 # |-------------------|-
                 # | Konstanten/       | |
                 # | Adressen          | |
                 # |...................| | letztes Modul
                 # | Variablen         | |
  Dieser Bereich # |-------------------|-
  wird vom       # :         .         :
  Linker auf der # :         .         :
  Diskette       # |-------------------|-
  abgelegt ( mit # | Konstanten/       | |
  Variablen! ).  # | Adressen          | |
                 # |...................| | erstes Modul ( GEMX )
                 # | Variablen         | |
                 # |-------------------|-
                 # | Programmcode      |  letztes Modul
                 # |-------------------|
                 # :         .         :
                 # :         .         :
                 # |-------------------|
                 # | Programmcode      |  erstes Modul ('GEMX')
                 # |-------------------|
                   | BASEPAGE          |
                   |___________________|



  Da Variablen und Konstanten mit negativen bzw. positiven Offsets zu
  CPU-Register A4 angesprochen werden (siehe:"Verwendung der Systemregister),
  das fr jedes Modul whrend der Programmausfhrung auf einen neuen Wert
  gesetzt wird (jedes Modul hat seinen eigenen Datenbereich), knnen ohne
  Verschieben von Speicherbereichen whrend des Programmstarts keine
  zusammenfassenden Variablen- und Konstantenbereiche fr das gesamte
  Programm erzeugt werden. Der Linker reserviert daher am Ende des
  Programmcodes fr alle Module hintereinander jeweils soviel Platz, wie
  wie jedes Modul fr Variablen und Konstanten bentigt; und da die Konstanten
  schlielich einen definierten Wert haben sollen, wird dieser ganze Platz
  im DATA-Segment reserviert, welches auch auf die Diskette geschrieben wird.
  Das BSS-Segment ist vllig leer, und die Variablen verbrauchen Speicherplatz
  auf der Diskette!

  Da ist es dann schon beinahe unerheblich, zu erwhnen, da der Linker
  nicht optimiert, d.h. selbst wenn nur eine einzige Konstante aus dem
  Definitionsmodul importiert wird, bindet der Linker das gesamte
  Implementationsmodul mit allen Prozeduren dazu.


  Das erste Modul, das von einem gelinkten Programm ausgefhrt wird, ist
  immer 'GEMX'. Dieses Modul bernimmt die Speicherrckgabe an das
  Betriebssystem und fhrt dann das eigentliche Programm aus (zuerst
  die Initialisierungsteile der vom Hauptprogramm direkt und indirekt
  importierten Module).

  Interessant ist der in 'GEMX.DEF' definierte Typ 'ExtInfo'; ein RECORD
  dieser Art wird nmlich vom Linker direkt hinter den GEMDOS-Programmvorspann
  geschrieben. Die RECORD-Elemente 'branch' und 'offset' stellen lediglich
  einen unbedingten Sprung hinter den RECORD dar, da das Programm mit der
  ersten Anweisung hinter dem GEMDOS-Vorspann gestartet wird.
  Wichtig sind jedoch die Elemente 'stackSize' und 'heapSize' (beides
  LONGINT); die Werte die hier stehen, werden nmlich zu der Menge an Speicher
  hinzugerechnet, die nicht ans Betriebssystem zurckgegeben wird (zusammen
  mit der Lnge des TEXT- und des DATA-Segmentes, das BSS-Segment scheint
  ja immer Null zu sein, siehe oben).

  'stackSize' legt, wie schon der Name sagt, die Gre des Stacks am Ende des
  reservierten Speicherbereichs fest. Vom Linker werden hier anscheinend
  konstant 20.000 Byte eingesetzt, was mehr als ausreichend ist, falls nicht
  gerade eine hochrekursive Prozedur mit vielen lokalen Variablen abgearbeitet
  wird. Aber Vorsicht: Es gibt keinen Stacktest, ein berlauf mit Zerstrung
  weiter unten liegender Speicherbereiche kann nicht abgefangen werden.

  'heapSize' ist der Speicherbereich, der von den Prozeduren "Allocate" und
  "Deallocate" aus 'Heap' zur Speicherverwaltung benutzt wird. Da dieser
  Bereich fest beim Start des Programms reserviert wird, und die Prozeduren
  weder berflssigen Speicher ans Betriebssystem zurckgeben noch bentigten
  anfordern, ist es mit der dynamischen Speicherbeschaffung zur Laufzeit
  nicht weit her: wenn der Bereich vollstndig belegt ist, kann kein weiterer
  mehr angefordert werden. Vom Linker wird fr 'heapSize' anscheinend konstant
  ein Wert von 10.000 eingesetzt, was fr die meisten Programme sicher zu
  wenig sein drfte.

  Wer also die Gre des Stacks oder des `dynamisch' verwalteten Speichers
  verndern will, kann dies z.B. mit einem Disk-Monitor tun oder auch mit
  einem selbstgeschriebenen Programm. Auf der Diskette haben die beiden Werte
  folgende Offsets relativ zur Programmdatei:

  - 'stackSize': $20...$23

  - 'heapSize' : $24...$27



  Assemblerbenutzung
  ========================================================================

  Testmodus
  ----------------------------------------------------------------------
  Bei eingeschaltetem Testmodus produziert der Compiler zustzlichen Code
  zur berprfung von Index- und Bereichsberschreitungen (auch bei
  Unterbereichstypen) und fehlenden RETURN-Anweisungen bei
  Funktionsprozeduren.
  Da der Compiler den Assemblercode nicht entschlsseln kann, fgt er
  natrlich auch keinen Code fr die Index- oder Bereichsberschreitung ein;
  am Ende einer Funktionsprozedur werden allerdings immer ein paar Bytes
  eingefgt, die einen Laufzeitfehler produzieren (-> 'Funktionsprozedur
  ohne RETURN'). Durch eine explizite RETURN-Anweisung wird dieser Code
  durch einen Sprungbefehl im wahrsten Sinne des Wortes umgangen.
  Codiert man nun in Assembler eine Funktionsprozedur, so mu entweder
  ein zustzlicher Sprungbefehl eingebaut werden, was aber nicht so einfach
  ist, da das Sprungziel per INLINE nicht erreichbar ist - es liegt
  praktisch innerhalb der END-Anweisung, die zweite Mglichkeit wre, den
  Funktionswert durch

        RETURN( VAL( Funtionstyp, REG( Register )))

  zurckzugeben. Schlielich kann man auch den Testmodus ausschalten, was
  nicht weiter tragisch ist, da in den Assemblercode sowieso keine weiteren
  Tests eingebaut werden (knnen). Hat man sowohl MODULA-Quelltext als
  auch INLINEs im Modul, sollten die MODULA-Teile unbedingt vorher MIT
  Testmodus ausgetestet werden (Achtung: In der Shell sind 'JA' und 'NEIN'
  fr den Indextest vertauscht).



  Variablenplazierung
  -----------------------------------------------------------------------
  Alle Variablen und Parameter beginnen an einer geraden Adresse, auch
  solche vom Typ CHAR, BOOLEAN oder Aufzhlungstypen (alles BYTE-Gren,
  siehe "Zugriff auf Parameter und lokale Variablen"); anders ist es zum
  Teil bei Variablen innerhalb eines RECORD's: Hier werden hintereinander
  definierte Variablen mit BYTE-Gre auch hintereinander abgelegt,
  nachfolgende Variablen mit Wort- oder grerem Speicherbedarf werden
  wieder auf einer geraden Adresse abgelegt, so da eventuell ein Dummy-Byte
  eingefgt wird. Variablen vom Typ ARRAY OF ... werden immer an einer
  geraden Adresse abgelegt, auch wenn der Grundtyp eine BYTE-Gre ist.
  Eine BYTE-Gre, die auf ein solches ARRAY mit ungerader Anzahl von Bytes
  folgt, wird trotzdem auf einer geraden Adresse abgelegt. Felder mit einer
  ungeraden Anzahl Bytes werden bzgl. des Speicherbedarfs immer auf eine
  gerade Anzahl aufgerundet (siehe auch: "Deklaration von Stringvariablen/
  typen"), was bei Benutzung von SIZE( ) deutlich wird (siehe
  "Standardprozedur SIZE").

  Vielleicht wird das ganze am folgenden Beispiel deutlich:

     VAR
       multi : RECORD
         long : LONGCARD;
         byte : BYTE;
         word : INTEGER;
         ch1  : CHAR;
         ch2  : CHAR;
         arr1 : ARRAY [0..2] OF CHAR; (* ungerade Anzahl Bytes *)
         ch3  : CHAR;
         arr2 : ARRAY [0..0] OF CHAR; (*  -"-  *)
       END;


  Fr diesen RECORD erhlt man unter Benutzung der Standardprozeduren
  ADR, SIZE und HIGH folgende Werte:


     Typ:                Offset: SIZE: HIGH:
     ---------------------------------------
     gesamter RECORD          0    18

     LONGCARD                 0     4
     BYTE                     4     1
     INTEGER                  6     2
     CHAR                     8     1
     CHAR                     9     1
     ARRAY [0..2] OF CHAR    10     4    2
     CHAR                    14     1
     ARRAY [0..0] OF CHAR    16     2    0

     Summe der Einzelgren:       16
     Theoretischer Speicherbedarf: 14  (die beiden ARRAY's ein Byte weniger)



  Verwendung der Systemregister
  -----------------------------------------------------------------------
  A4 -> sog. 'Modulbasis'; mit einem Offset zu diesem Register werden
        globale Variablen des Moduls (negativer Offset), Stringkonstanten
        und Adressen der importierten Prozeduren (positiver Offset)
        angesprochen.
        Da mit der Adressierungsart  d16(A4)  gearbeitet wird, ergibt sich
        die vor allem fr Variablen wichtige Einschrnkung von maximal
        32 kB. D.h. eine Felddeklaration von 8000 (und ein paar
        zerquetschten) LONGINT's schpft bereits die fr ein Modul mgliche
        Variablenmenge aus !
        Das Register wird zu Beginn jeder Prozedur mit der Adresse des
        jeweiligen Moduls geladen. Bei verschachtelten Prozeduren ist das
        nicht ntig (es wurde schon von der Mutterprozedur erledigt).

  A5 -> wird im Zusammenhang mit Koroutinen bentigt, nheres ist mir nicht
        bekannt.

  A6 -> mit einem Offset zu diesem Register werden die aktuellen Parameter,
        der Funktionswert (positiver Offset ab 12) und die lokalen
        Variablen (negativer Offset) einer Prozedur angesprochen.

  A7 -> 'normaler' Stack fr Registerretten, Prozeduraufrufe...


  Prozeduraufrufe
  -----------------------------------------------------------------------
  Die Parameterbergabe findet vollstndig ber den Stack statt, auch
  Funktionswerte werden nicht in einem Register, sondern auf dem Stack
  zurckgegeben (Bei 'C' ist z.B. die Rckgabe in Register D0 Standard).
  Value-Parameter (auer den weiter unten besprochenen offenen Feldern)
  werden vom Aufrufer komplett auf den Stack kopiert, bei VAR-Parametern
  ists nur die Adresse.
  Die Parameter werden in der Reihenfolge ihres Auftretens in der Liste
  auf dem Stack abgelegt, d.h. der erste in der Liste stehende Parameter
  bzw. seine Adresse wird als erstes auf den Stack gebracht. Ist die
  aufgerufene Prozedur eine Funktion, wird der Platz fr die Rckgabe
  des Funktionswertes vor allen Parametern auf dem Stack reserviert
  (Der Funktionswert ist sozusagen der nullte Parameter).

  Jede Prozedur rumt - neben den lokalen Variablen - ihre eigenen Parameter
  vom Stack, so da der Aufrufer die von ihm abgelegten aktuellen Parameter
  nicht mehr entfernen mu, auer einem evtl. Funktionswert.
  ACHTUNG: Das ist auch nicht Standard, smtliche 'C'-Compiler und wohl
  auch diverse M2-Compiler lassen die Parameter vom Aufrufer entfernen !

  Bei Prozedureintritt werden keine Register gerettet; wenn Werte in den
  Registern stehen, die nach dem Aufruf noch bentigt werden, mssen sie
  vom Aufrufer vor dem Aufruf auf dem Stack gesichert werden - normalerweise
  werden aber keine Werte in Registern gehalten auer innerhalb von
  Ausdrcken (Zwischenwerte) und WITH-Anweisungen (WITH-Basis in
  Adreregister A3 bzw. A2,A1,A0 bei verschachteteln WITH-Anweisungen, wird
  aber vor einem Prozeduraufruf auf dem Stack gesichert).

  Die Register D0-D7,A0-A3 sind fr die Verwendung von Assemblerprogrammen
  frei, solange innerhalb einer Prozedur nicht mit einer Mischung aus MODULA-
  und INLINE-Code gearbeitet wird, ansonsten mu ein Disassemblerlisting ber
  die Verwendung der Register im MODULA-Code Aufschlu geben.
  Die Register A4-A7 werden vom System bentigt und drfen daher innerhalb
  einer Prozedur nicht verndert werden (auer A7 als Parameter- und
  Sicherungsstack). Es wird jedoch nicht erwartet, da diese Register beim
  Eintritt in die Prozedur einen bestimmten Wert haben (auer vielleicht
  A5 - wei ich nicht - und natrlich A7), sie werden zu Beginn mit den
  entsprechenden Werten geladen.

  Wird eine verschachtelte (lokale) Prozedur aufgerufen, wird das Register
  A6 - der Zeiger auf den eigenen lokalen Datenraum - vom Aufrufer auf dem
  Stack abgelegt, so da die aufgerufene Prozedur Zugriff auf die lokalen
  Variablen und Parameter des Aufrufers hat.



  Zugriff auf Parameter und lokale Variablen
  -----------------------------------------------------------------------
  Die Adressierung von Parametern und lokalen Variablen soll an folgender
  hypothetischen Prozedur erlutert werden:


    PROCEDURE p ( par1   : pTyp1;
                  par2   : pTyp2;
                    .
                    .
                  parNm1 : pTypNm1;
                  parN   : pTypN   ): TypReturn;

      VAR  lokal1   : lTyp1;
           lokal2   : lTyp2;
              .
              .
           lokalNm1 : lTypNm1;
           lokalN   : lTypN;

      END p;


  Die Parameter dieser Prozedur lassen sich im Assemblertext folgendermaen
  mit einem (positiven) Offset zu Register A6 ansprechen (Das 'm' bei
  den Parameternamen steht dabei fr `Minus', also z.B  'parNm1' fr den
  vorletzten von N Parametern):

    OffsParN    EQU  12                            ; konstant
    OffsParNm1  EQU  OffsParN   + sizeof( parN )
    OffsParNm2  EQU  OffsParNm1 + sizeof( parNm1 )
          .
          .
    OffsPar1    EQU  OffsPar2   + sizeof( par2 )
    RETURN      EQU  OffsPar1   + sizeof( par1 )   ; Funktionswert


  Die lokalen Variablen lassen sich mit folgenden Offsets zu A6 ansprechen:

    OffsLok1    EQU  - sizeof( lokal1 )            ; negative Offsets
    OffsLok2    EQU  OffsLok1   - sizeof( lokal2 )
          .
          .
    OffsLokNm1  EQU  OffsLokNm2 - sizeof( lokalNm2 )
    OffsLokN    EQU  OffsLokNm1 - sizeof( lokalNm1 )


  sizeof( ) steht dabei fr den - scheinbaren - Speicherbedarf der
  jeweiligen Variable. VAR-Parameter bentigen immer 4 Bytes (Adressen),
  und Byte-Gren schlagen IMMER mit zwei Bytes zu Buche, auch wenn
  sie hintereinander stehen; das kommt durch die Art der Parameterbergabe,
  denn ein

    MOVE.B  #byte, -(SP)

  verndert den Stackpointer immer um ZWEI Bytes (Schutz vor ungeraden
  Return-Adressen).

  Dabei liegen die Byte-Werte aber immer auf einer geraden Adresse, so da
  sie bei einem Wortzugriff im hherwertigen Byte liegen; deshalb sollte
  innerhalb des Assemblertextes auch nur mit einem Bytezugriff auf solche
  Parameter gearbeitet werden. Das ist besonders bei einer Konvertierung
  in einen Wort-Wert zu beachten (ORD( )):

    MOVE.W   OffsByte(A6), D0   ; falsch, nicht ORD

    CLR.W    D0                 ; statt EXT.W, da Byte-Gren ohne Vorzeichen
    MOVE.B   OffsByte(A6), D0   ; richtig

  Das gilt auch vor allem, wenn der Wert gleich wieder als Parameter einer
  aufzurufenden Prozedur dienen soll, da die automatische Verringerung des
  Stackpointers das Byte in der oberen Hlfte des Wortes (gerade Adresse)
  plaziert :

    p2( ORD( byte )); :

    MOVE.B   byte(A6), -(SP)    ; FALSCH !
    BSR      p2

    CLR.W    D0                 ; richtig
    MOVE.B   byte(A6), D0       ;
    MOVE.W   D0, -(SP)          ;
    BSR      p2



  Value-Parameter und lokale Variablen knnen direkt angesprochen werden:

    MOVE.W   OffsPar(A6), D0    ; Value-Parameter lesen
    MOVE.W   #wert, OffsPar(A6) ; Value-Parameter schreiben


  Soll auf VAR-Parameter zugegriffen werden, mu zuerst die Adresse des
  Parameters in ein Adreregister geladen werden, die bertragung erfolgt
  dann durch eine indirekte Zuweisung:

    MOVEA.L  OffsVar(A6), A0    ; A0 -> Parameter
    MOVE.W   (A0), D0           ; VAR-Parameter lesen
    MOVE.W   #wert, (A0)        ; VAR-Parameter schreiben


  Die Rckgabe ber den Funktionswert funktioniert dagegen wie der Zugriff
  auf einen Value-Parameter oder eine lokale Variable:

    MOVE.W   #wert, RETURN(A6)




  Umgebung der Systemregister A4 und A6 zur Laufzeit
  ----------------------------------------------------------------------

  Nach dem BEGIN einer Prozedur sieht der Stack wie folgt aus:


    -- nur auf diesen Bereich hat die Prozedur direkten Zugriff
   |
   V               ___________________________
   #           -->|  Funktionswert            |<-- (nullter Parameter)
   #          |   |---------------------------|
   #          |   |                           |<-- erster Parameter
   #          |   :  akt. Parameter           : ^
   #          |   :                           : |
   #          |   |                           | |
   #          +---|-=========================-|<-- letzter Parameter
   #          |   || RTN-Adresse             ||
   #   konst. |   ||-------------------------||
   #   Offset |12 || altes A4                ||<-- Modulbasis Aufrufer
   #          |   ||-------------------------||    (in Proz. nicht benutzt)
   #          |   || altes A6                ||<-- Stack-Frame Aufrufer
   #  A6 -----+-->|-=========================-|    (in Proz. nicht benutzt)
   #          |   |                           |<-- erste lokale Variable
   #   neg.   |   :  lokale Variablen         : ^
   #   Offsets|   :                           : |
   #          |   |                           | |
   #           -->|---------------------------|<-- letzte lokale Variable
                  |                           |<-- erster Par. dieser Art
                  :  VALUE-Parameter der Art: : ^
                  :  ARRAY OF <Typ>           : |
                  |                           | |
      A7 -------->|---------------------------|<-- letzter Par. dieser Art
                  :                           :
                  :  Platz fr Proz.aufrufe   :
                  :                           :
                               |
                               |
                               V
                            fallende
                            Adressen


  Der umrahmte Teil gilt nur fr unverschachtelte Prozeduren. Wird eine
  verschachtelte Prozedur (lokal innerhalb einer anderen definiert)
  aufgerufen, ist dieser Teil etwas verndert, damit die Prozedur Zugriff
  auf die Variablen der umgebenden Prozedur hat. Gibt es mehrfach
  verschachtelte Prozeduren, mu entsprechend oft ber den Stack-Frame
  der Umgebung dereferenziert werden, da dieser immer nur auf die nchst-
  hhere Ebene verweist (verkettete Liste):


              +---|-=========================-|
              |   || A6 Umgebung             ||<-- Stack-Frame Umgebung
       konst. |   ||-------------------------||    (stat. Vorgnger)
       Offset |12 || RTN-Adresse             ||
              |   ||-------------------------||
              |   || A6 Aufrufer             ||<-- Stack-Frame Aufrufer
      A6 -----+-->|-=========================-|    (dyn. Vorgnger,
                                                    in Proz. nicht benutzt)


  Der dynamische Vorgnger ist also die aufrufende Prozedur, der statische
  Ist die unmittelbar umgebende bei verschachtelten Prozeduren.
  Auch wenn sich die eingebettete Prozedur rekursiv aufruft, wird der
  Stack-Frame des statischen Vorgngers wieder bergeben.
  Zugriffe auf Objekte der Umgebung sind so natrlich relativ zeitaufwendig.


  Bei offenen Feldparametern (ARRAY OF <Typ>) kann die Gre der Parameter
  erst zur Laufzeit bestimmt werden; vom Aufrufer wird daher vor der Adresse
  des Feldes noch der maximale Index (HIGH(..)) auf den Stack gebracht.
  Es wird immer nur die Adresse eines offenen Feldes bergeben, egal ob es
  ein Value- oder VAR-Parameter ist, im Gegensatz zu Parametern aller anderen
  Typen. Ist es ein Value-Parameter, so wird das Feld von der AUFGERUFENEN
  Prozedur unterhalb ihrer lokalen Variablen auf den Stack kopiert,
  entsprechend wird die Adresse des nun lokalen ARRAY's im Bereich der
  aktuellen Parameter auf den neuen Wert gesetzt; es wird nur ber diese
  Adresse im akt.Parameter-Bereich auf solche Feldparameter zugegriffen.

  Als Beispiel folgt hier ein Disassemblerlisting einer Prozedur
  mit einem Value-Parameter vom Typ ARRAY OF CHAR:


        MOVE.L  A4,-(SP)      ; Modulbasis des Aufrufers retten
        MOVEA.L $44(PC),A4    ; a4 -> eigene Modulbasis
        LINK    A6,#-$2       ; Platz fr lokale Variable
        MOVE.W  $10(A6),D2    ; d2 := HIGH( string )

; ---------   String in lokale Variable kopieren

        ADDQ.W  #$1,D2        ; Lnge des Feldes ( = HIGH(..)+1 )
        BTST    #$0,D2        ; ungerade Anzahl ?
        BEQ.S   copy          ; B: nein, ok
        ADDQ.W  #$1,D2        ; ja, gerade machen, damit gerade Adresse
copy:   SUBA.W  D2,SP         ; SP -> lokale Stringvariable
        MOVEA.L $C(A6),A3     ; a3 -> string
        MOVE.L  SP,$C(A6)     ; Adr. des lokalen Strings merken, die Adr.
                              ; des Originalstrings wird nicht mehr gebraucht
        MOVEA.L SP,A2         ; a2 -> lokale Stringvariable
        SUBQ.W  #$1,D2        ; wegen dbra
copylp: MOVE.B  (A3)+,(A2)+   ; String kopieren
        DBRA    D2, copylp    ;

; ---------------------------------------------
;      Prozedurrumpf
; ---------------------------------------------

procend:UNLK    A6              ; lokale Variablen abbauen
        MOVEA.L (SP)+,A4        ; a4 -> Modulbasis des Aufrufers
        MOVEA.L (SP)+,A0        ; a0 -> RTN-Adr.
        ADDQ.L  #$6,SP          ; Parameter vom Stack entfernen
        JMP     (A0)            ; END  Length; Rckkehr zum Aufrufer




  Die Umgebung von Register A4 zur Laufzeit sieht so aus:


                      M o d u l t a b e l l e
                      -----------------------

                  m : Anzahl importierter Module
                  p : Anzahl exportierter Prozeduren


                     ___________________________
                  ->|                           |<-- letzter String
                 |  : Stringkonstanten          : ^
                 |  :                           : |
                 |  |                           | |
  (m+2 + p+1)*4 -+--|---------------------------|<-- erster String
                 |  | 'System'-Modulbasis       |
                 |  |---------------------------|
                 |  |                           |<-- letztes imp. Modul
                 |  : Basen der explizit        : ^
                 |  : importierten Module       : |
                 |  | ( m*4 Bytes )             | |
                 |  |---------------------------|<-- erstes imp. Modul
                 |  | eigene Modulbasis         |
        (p+1)*4 -+--|---------------------------|
                 |  |                           |<-- letzte exp. Prozedur
                 |  : Adressen der exportierten : ^
                 |  : Prozeduren ( p*4 Bytes )  : |
                 |  |                           | |
              4 -+--|---------------------------|<-- erste exp. Prozedur
                 |  | Adresse Modulrumpf        |
       A4 -------+->|---------------------------|<-- "Modulbasis"
                 |  | Flags (16 Bit)            | Bit 8: Modul initialisiert
             -2 -+--|---------------------------|
                 |  |                           |
                 |  :       ? ? ?               :
                 |  :                           :
                 |  |                           |
    konst.  -28 -+--|---------------------------|
                 #  |                           |<-- erste exp. Variable
                 #  : exportierte Variablen     : ^
                 #  :                           : |
      maximal    #  |                           | |
    32768 - 28   #  |---------------------------|<-- letzte exp. Variable
    = 32740      #  |                           |<-- erste lokale Variable
    Bytes        #  : modullokale Variablen     : ^
    Variablen    #  :                           : |
    pro Modul !! #  |                           | |
                 #--|---------------------------|<-- letzte lok. Variable
                    |                           |
                    :                           :
                    :            |              :
                                 |
                                 V

                         fallende Adressen



  Aufbau einer OBM-Datei
  ========================================================================

  Ein vom Compiler bersetztes Modul mit der Endung .OBM besteht aus vier
  Blcken, wobei am Anfang jedes Blocks eine Kennung fr den jeweiligen
  Blocktyp und die Lnge des Blockes stehen. Der Reihenfolge nach existieren:

   1. Modulkopf
      Name des Moduls, Gre von Code, Variablen, Anzahl exp. Prozeduren...
   2. Import
      Namen und Versionsnummern der importierten Module
   3. Code
      Hier steht der ausfhrbare Code
   4. Daten
      Hier stehen die Stringkonstanten und Adressen der exp. Prozeduren
      und zur Laufzeit die Adressen der imp. Module.


  Offset  Lnge  Name       Funktion
  ------------------------------------------------------------------------

  --> 'feste' (HEADLEN ist bisher jedenfalls konstant) Adressen:

  $00      2     HEADSec    = $0001, Kennung fr Modulkopf
  $02      2     HEADLEN    = $0022, Gre des Modulkopfes
  $04      2     RES1       = $0000, ???
  $06     16     modName    Modulname, mit Nullen aufgefllt
  $16      6     version    bersetzungsdatum des zugehrigen DEF-Moduls;
                            wird fr Versionsprfung benutzt
  $1C      2     codeSize   Gre des ausfhrbaren Codes + 4
  $1E      2     varSize    Gre des Variablenspeichers + 28
  $20      2     constSize  Gre der Stringkonstanten (andere Konstanten
                            werden direkt im Code verwendet)
  $22      2     numProcs   Anzahl exportierter Prozeduren + 1 ( Modulinit )
  $24      2     numMods    Anzahl importierter Module + 2 ('System',eigenes )
  $26      2     IMPORTSec  = $0002, Kennung fr Importe
  $28      2     IMPORTLEN  Gre der nachfolgenden Importe
  $2A
         Importe, bestehend aus :
            'modName'
            'version',
         zuletzt "System" mit <version> = 0

  --> variable Adressen:

  $2A+<IMPORTLEN>
           2     CODESec    = $0003, Kennung fr Codebereich

  $2C+<IMPORTLEN>
           2     CODELEN    = <codeSize>

  $2E+<IMPORTLEN>
           4     modBase    zur Laufzeit: Adresse der Modulbasis; sonst NOP,NOP

  $2E+<IMPORTLEN>+4

         ausfhrbarer Code

  $2E+<IMPORTLEN>+<CODELEN>
           2     DATASec    = $0004, Kennung fr Datenbereich

  $2E+<IMPORTLEN>+<CODELEN>+2
           2     DATALEN    Gre des folgenden Bereichs bis zum Dateiende
                            ( = <constSize> + (<numProcs>+<numMods>)*4 )

  $2E+<IMPORTLEN>+<CODELEN>+4     "Modulbasis"

         Ab hier Adretabelle;
         Prozedur-Adressen relativ zu 'modBase',
         Adr. der imp. Module noch Null ( erst zur Laufzeit belegt ).
         Aufbau siehe Bild fr A4 zur Laufzeit ( pos. Offsets )

  $2E+<IMPORTLEN>+<CODELEN>+4+(<numProcs>+<numMods>)*4

         Ab hier Stringkonstanten
         ( jeweils mit Nullbyte abgeschlossen, an gerader
           Adresse beginnend ).
         leere Strings werden hier auch durch zwei Nullbytes
         abgelegt ( Stringende und gerade Adr. )

  $2E+<IMPORTLEN>+<CODELEN>+4+<DATALEN>

         Dateiende



  Lokale Module werden im Code nicht gesondert reprsentiert; aus ihnen
  exportierte Prozeduren werden wie andere modullokale Prozeduren behandelt;
  die Initialisierungsteile stehen in der Reihenfolge des Auftretens der
  Module vor dem Initialisierungsteil des Hauptmoduls.

  Einige Systemmodule (COMPILE.OBM, DEBUG.OBM, M2EDITOR.OBM, M2SHELL.OBM)
  bestehen aus mehreren solcher OBM-Dateien hintereinander; unklar ist, ob es
  fr sowas eine spezielle Anweisung des Compilers gibt, oder das ganze
  'zu Fu' fabriziert wurde.
